#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>

#define TB_TTY_NAME   "/dev/tty"

#define TB_PATH_MAX   4096

#define tb_malloc     malloc
#define tb_free       free

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

#define BREAK_ON(condi)                                                        \
    if (condi) {                                                               \
        break;                                                                 \
    }
#define RETURN_ON(condi)                                                       \
    if (condi) {                                                               \
        return;                                                                \
    }
#define IS_STR_EMPTY(s)        ((s) == NULL || (s)[0] == '\0')

#define TB_CAP_F1              0
#define TB_CAP_F2              1
#define TB_CAP_F3              2
#define TB_CAP_F4              3
#define TB_CAP_F5              4
#define TB_CAP_F6              5
#define TB_CAP_F7              6
#define TB_CAP_F8              7
#define TB_CAP_F9              8
#define TB_CAP_F10             9
#define TB_CAP_F11             10
#define TB_CAP_F12             11
#define TB_CAP_INSERT          12
#define TB_CAP_DELETE          13
#define TB_CAP_HOME            14
#define TB_CAP_END             15
#define TB_CAP_PGUP            16
#define TB_CAP_PGDN            17
#define TB_CAP_ARROW_UP        18
#define TB_CAP_ARROW_DOWN      19
#define TB_CAP_ARROW_LEFT      20
#define TB_CAP_ARROW_RIGHT     21
#define TB_CAP_BACK_TAB        22
#define TB_CAP__COUNT_KEYS     23
#define TB_CAP_ENTER_CA        23
#define TB_CAP_EXIT_CA         24
#define TB_CAP_SHOW_CURSOR     25
#define TB_CAP_HIDE_CURSOR     26
#define TB_CAP_CLEAR_SCREEN    27
#define TB_CAP_SGR0            28
#define TB_CAP_UNDERLINE       29
#define TB_CAP_BOLD            30
#define TB_CAP_BLINK           31
#define TB_CAP_ITALIC          32
#define TB_CAP_REVERSE         33
#define TB_CAP_ENTER_KEYPAD    34
#define TB_CAP_EXIT_KEYPAD     35
#define TB_CAP_DIM             36
#define TB_CAP_INVISIBLE       37
#define TB_CAP__COUNT          38

/* Some hard-coded caps */
#define TB_HARDCAP_ENTER_MOUSE "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
#define TB_HARDCAP_EXIT_MOUSE  "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
#define TB_HARDCAP_STRIKEOUT   "\x1b[9m"
#define TB_HARDCAP_UNDERLINE_2 "\x1b[21m"
#define TB_HARDCAP_OVERLINE    "\x1b[53m"

/* Input modes (bitwise) (`tb_set_input_mode`) */
#define TB_INPUT_CURRENT       0
#define TB_INPUT_ESC           (0x01)
#define TB_INPUT_ALT           (0x02)
#define TB_INPUT_MOUSE         (0x04)


typedef struct {
    char *base;
    size_t len;
    const char *path;
    int16_t string_off_pos;
    int16_t string_off_len;
    int16_t string_tab_pos;
    int16_t string_tab_len;
    const char *caps[TB_CAP__COUNT];
} tb_terminfo_t;

typedef struct {
    int tty_fd;
    struct termios attr; // backup term origin attr

    struct {
        char *term;
        char *terminfo;
    } env;

    tb_terminfo_t terminfo;
} tb_context_t;

typedef struct {
    char *base;
    size_t used;
    size_t size;
} tb_buf_t;

static int load_terminfo_by_path(tb_context_t *tb, const char *path) {
    char buf[TB_PATH_MAX];
    char *term = tb->env.term;

    snprintf(buf, sizeof(buf), "%s/%c/%s", path, term[0], term);

    FILE *fp = fopen(buf, "rb");
    if (fp == NULL) {
        return -1;
    }

    struct stat st;
    if (fstat(fileno(fp), &st) != 0) {
        fclose(fp);
        return -2;
    }

    size_t fsize = st.st_size;

    char *data = (char *)tb_malloc(fsize);
    if (!data) {
        fclose(fp);
        return -3;
    }

    if (fread(data, 1, fsize, fp) != fsize) {
        fclose(fp);
        tb_free(data);
        return -4;
    }

    tb->terminfo.base = data;
    tb->terminfo.len = fsize;

    fclose(fp);
    return 0;
}

static int terminfo_load(tb_context_t *tb) {
    const char *paths[] = {
        "/usr/local/etc/terminfo",
        "/usr/local/share/terminfo",
        "/usr/local/lib/terminfo",
        "/etc/terminfo",
        "/usr/share/terminfo",
        "/usr/lib/terminfo",
        "/usr/share/lib/terminfo",
        "/lib/terminfo",
    };

    tb->env.term = getenv("TERM");
    tb->env.terminfo = getenv("TERMINFO");

    tb->terminfo.base = NULL;

    for (int i = 0; i < ARRAY_SIZE(paths); i++) {
        if (load_terminfo_by_path(tb, paths[i]) == 0) {
            tb->terminfo.path = paths[i];
            break;
        }
    }

    if (tb->terminfo.base == NULL) return -1;

    return 0;
}

static const char *terminfo_get_string(tb_terminfo_t *terminfo, int cap_index) {
    const int str_byte_index = cap_index * (int)sizeof(int16_t);
    if (str_byte_index >= (int)terminfo->string_off_len * (int)sizeof(int16_t))
    {
        // An offset beyond the table indicates absent
        // See `convert_strings` in tinfo `read_entry.c`
        return "";
    }
    const int16_t *string_off =
        (int16_t *)(terminfo->base + (int)terminfo->string_off_pos +
                str_byte_index);

    if ((char *)string_off >= terminfo->base + terminfo->len) {
        // str_offset points beyond end of entry
        // Truncated/corrupt terminfo entry?
        return NULL;
    }
    if (*string_off < 0 || *string_off >= terminfo->string_tab_len) {
        // A negative offset indicates absent
        // An offset beyond the table indicates absent
        // See `convert_strings` in tinfo `read_entry.c`
        return "";
    }
    if (((size_t)((int)terminfo->string_tab_pos + (int)*string_off)) >=
            terminfo->len)
    {
        // string points beyond end of entry
        // Truncated/corrupt terminfo entry?
        return NULL;
    }

    const char *string =
        terminfo->base + (int)terminfo->string_tab_pos + (int)*string_off;
    return string;
}

static int terminfo_parse_caps(tb_terminfo_t *terminfo) {

    const int16_t cap_index[] = {
        66,  /* kf1 (TB_CAP_F1) */
        68,  /* kf2 (TB_CAP_F2) */
        69,  /* kf3 (TB_CAP_F3) */
        70,  /* kf4 (TB_CAP_F4) */
        71,  /* kf5 (TB_CAP_F5) */
        72,  /* kf6 (TB_CAP_F6) */
        73,  /* kf7 (TB_CAP_F7) */
        74,  /* kf8 (TB_CAP_F8) */
        75,  /* kf9 (TB_CAP_F9) */
        67,  /* kf10 (TB_CAP_F10) */
        216, /* kf11 (TB_CAP_F11) */
        217, /* kf12 (TB_CAP_F12) */
        77,  /* kich1 (TB_CAP_INSERT) */
        59,  /* kdch1 (TB_CAP_DELETE) */
        76,  /* khome (TB_CAP_HOME) */
        164, /* kend (TB_CAP_END) */
        82,  /* kpp (TB_CAP_PGUP) */
        81,  /* knp (TB_CAP_PGDN) */
        87,  /* kcuu1 (TB_CAP_ARROW_UP) */
        61,  /* kcud1 (TB_CAP_ARROW_DOWN) */
        79,  /* kcub1 (TB_CAP_ARROW_LEFT) */
        83,  /* kcuf1 (TB_CAP_ARROW_RIGHT) */
        148, /* kcbt (TB_CAP_BACK_TAB) */
        28,  /* smcup (TB_CAP_ENTER_CA) */
        40,  /* rmcup (TB_CAP_EXIT_CA) */
        16,  /* cnorm (TB_CAP_SHOW_CURSOR) */
        13,  /* civis (TB_CAP_HIDE_CURSOR) */
        5,   /* clear (TB_CAP_CLEAR_SCREEN) */
        39,  /* sgr0 (TB_CAP_SGR0) */
        36,  /* smul (TB_CAP_UNDERLINE) */
        27,  /* bold (TB_CAP_BOLD) */
        26,  /* blink (TB_CAP_BLINK) */
        311, /* sitm (TB_CAP_ITALIC) */
        34,  /* rev (TB_CAP_REVERSE) */
        89,  /* smkx (TB_CAP_ENTER_KEYPAD) */
        88,  /* rmkx (TB_CAP_EXIT_KEYPAD) */
        30,  /* dim (TB_CAP_DIM) */
        32,  /* invis (TB_CAP_INVISIBLE) */
    };

    // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a
    // description of this behavior.

    // Ensure there's at least a header's worth of data
    if (terminfo->len < 6 * (int)sizeof(int16_t)) return -1;

    int16_t *header = (int16_t *)terminfo->base;
    /*
       header[0] the magic number (octal 0432 or 01036)
       header[1] the size, in bytes, of the names section
       header[2] the number of bytes in the boolean section
       header[3] the number of short integers in the numbers section
       header[4] the number of offsets (short integers) in the strings section
       header[5] the size, in bytes, of the string table
       */

    // Legacy ints are 16-bit, extended ints are 32-bit
    const int bytes_per_int = header[0] == 01036 ? 4  // 32-bit
        : 2; // 16-bit

    /* Between the boolean section and the number section, a null byte will be
       inserted, if necessary, to ensure that the number section begins on an
       even byte */
    const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0;
    /*
       const int pos_str_offsets =
       (6 * sizeof(int16_t)) // header (12 bytes)
       + header[1]           // length of names section
       + header[2]           // length of boolean section
       + align_offset +
       (header[3] * bytes_per_int); // length of numbers section

       const int pos_str_table =
       pos_str_offsets +
       (header[4] * sizeof(int16_t)); // length of string offsets table
       */
    terminfo->string_off_pos = (6 * sizeof(int16_t) + header[1] + header[2] + align_offset + (header[3] * bytes_per_int));
    terminfo->string_tab_pos = terminfo->string_off_pos + header[4] * sizeof(int16_t);
    terminfo->string_off_len = header[4];
    terminfo->string_tab_len = header[5];

    // Load caps
    for (int i = 0; i < TB_CAP__COUNT; i++) {
        const char *cap = terminfo_get_string(terminfo, cap_index[i]);
        if (!cap) {
            // Something is not right
            return -1;
        }
        terminfo->caps[i] = cap;
    }

    return 0;
}

static int term_get_attr() {
}

static int term_set_attr() {
}

static int term_set_raw() {
}

int tb_init(tb_context_t *tb) {
    int ret;
    tb->tty_fd = open(TB_TTY_NAME, O_RDWR);
    if (tb->tty_fd < 0) {
        printf("tty open failed\n");
        return -1;
    }

    if (tcgetattr(tb->tty_fd, &tb->attr) != 0) {
        return -2;
    }

    struct termios term = {0};

    cfmakeraw(&term);
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;

    if (tcsetattr(tb->tty_fd, TCSAFLUSH, &term) != 0) {
        return -1;
    }

    RETURN_ON(terminfo_load(tb))
    RETURN_ON(terminfo_parse_caps(&tb->terminfo))

    return 0;
}

int tb_deinit(tb_context_t *tb) {
    if (tcsetattr(tb->tty_fd, TCSAFLUSH, &tb->attr) != 0) {
        return -1;
    }
}

static int buf_alloc()
{
}

static void buf_free()
{
}

static int buf_puts(tb_buf_t *b, const char *str)
{
    int len = strlen(str);
}

static int buf_flush(tb_buf_t *b, int fd)
{
}

int tb_set_input_mode(uint32_t mode)
{
#if 0
    if (mode == TB_INPUT_CURRENT) return global.input_mode;

    int esc_or_alt = TB_INPUT_ESC | TB_INPUT_ALT;
    if ((mode & esc_or_alt) == 0) {
        // neither specified; flip on ESC
        mode |= TB_INPUT_ESC;
    } else if ((mode & esc_or_alt) == esc_or_alt) {
        // both specified; flip off ALT
        mode &= ~TB_INPUT_ALT;
    }

    if (mode & TB_INPUT_MOUSE) {
        bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE);
        bytebuf_flush(&global.out, global.wfd);
    } else {
        bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE);
        bytebuf_flush(&global.out, global.wfd);
    }

    global.input_mode = mode;
    return TB_OK;
#endif
}

int tb_loop() {
}

int tb_show() {
}

int main() {
    tb_context_t app = {0};
    tb_init(&app);

    printf("Hello World, with: %s\n", app.terminfo.path);

    tb_deinit(&app);
    return 0;
}
